﻿using System;
using System.Collections.Generic;
using Ivory.Libs;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; 

namespace Ivory
{
    public sealed class ScreenManager : DrawableGameComponent 
    {
        private enum ManagerScreenState {
            Splashes = 0,
            Menu = 1,
            MainMenu = 2,
            Game = 3,
        };

        //Fields
        private ManagerScreenState _mScreenState = ManagerScreenState.Splashes; //Splashes by default
        public Config Config;

        private List<SplashScreenBase> _splashScreens;
        private int _splashCounter = 0;

        private Dictionary<string, GameScreen> _gameScreens;
        private Dictionary<string, MenuScreen> _menuScreens;
        private MainMenuScreen _MainMenuScreen;
        private string _screenKeyHolder = null;
        private GraphicsDeviceManager _gdm;
        private SpriteBatch _BatchDevice;
        private InputControl _InputController;

		private IvoryLog _ManagerLog; 
		public IvoryLog ManagerLog
		{
			get { return _ManagerLog; }
			set { _ManagerLog = value; }
		} 		
        
        //Construct
        /// <summary>
        /// Place this in the Constructor of your Game
        /// </summary>
        public ScreenManager(Game game, string configPath)
            : base(game)
        {  
            _splashScreens = new List<SplashScreenBase>();
            _gameScreens = new Dictionary<string, GameScreen>();
            _menuScreens = new Dictionary<string, MenuScreen>();

            _mScreenState = ManagerScreenState.Splashes;
            _splashCounter = 0;
            _screenKeyHolder = null;
            _InputController = new InputControl(game);

            _ManagerLog = new IvoryLog(game.Content.RootDirectory + "\\managerslog.log", false);
            _ManagerLog.ShowTimeStamp = true;
            _ManagerLog.Write("Manager Started");

            Config = new Config(game.Content.RootDirectory + "/" + configPath);
            setupFromConfig();

            //Register an event to tell us when we're exiting
            Game.Exiting += new EventHandler<EventArgs>(OnGameExiting);
        }
        
        //Methods
        #region @@## Screen Mutators ##@@

        public bool AddSplashScreen(SplashScreen ss) {
            
            if(_splashScreens.Contains(ss))
                throw new IvoryFrameworkError(Errors.KeyClashException(typeof(SplashScreenBase), "splash"));                

            ss.Manager = this;
            _splashScreens.Add(ss);
            return true;
        }
        public bool AddGameScreen(string Key, GameScreen screen) {
            if (_gameScreens.ContainsKey(Key))
                throw new IvoryFrameworkError(Errors.KeyClashException(typeof(GameScreen), Key)); 

            screen.Manager = this;
            _gameScreens.Add(Key, screen);
            return true;
        }
        public bool AddMenuScreen(string Key, MenuScreen screen) {
            if (_menuScreens.ContainsKey(Key))
                throw new IvoryFrameworkError(Errors.KeyClashException(typeof(MenuScreen), Key));
                        
            screen.Manager = this; //assign manager
            _menuScreens.Add(Key, screen);
            return true;
        }
        public void SetMainMenu(MainMenuScreen screen)
        {
            this._MainMenuScreen = screen;
            _MainMenuScreen.Manager = this;
        }

        private void SwitchToo(string key, ManagerScreenState state)
        {
            //If we are currenty on a game screen, unload it
            if (_mScreenState == ManagerScreenState.Game)
                _gameScreens[_screenKeyHolder].unload();

            switch (state)
            {
                case ManagerScreenState.Game:
                    {
                        _mScreenState = state;
                        _screenKeyHolder = key;
                        _gameScreens[_screenKeyHolder].loadScreen();
                        break;
                    }
                case ManagerScreenState.Menu:
                    {
                        _mScreenState = state;
                        _screenKeyHolder = key;                        
                        _menuScreens[_screenKeyHolder].loadScreen(); //do i need this?
                        break;
                    }
                case ManagerScreenState.MainMenu:
                    {
                        _mScreenState = state;
                        break;
                    }
                


                default:
                    throw new Exception("0x002");
            }

            
            //1/ unload the previous screen   
        }
        public bool SwitchToGameScreen(string Key)
        {
            if (!_gameScreens.ContainsKey(Key))
                throw new IvoryFrameworkError(Errors.KeyNotFoundException(Key));

            SwitchToo(Key, ManagerScreenState.Game);
            return true;
        }
        public bool SwitchToMenuScreen(string Key)
        {
            if (!_menuScreens.ContainsKey(Key))
                throw new IvoryFrameworkError(Errors.KeyNotFoundException(Key));

            SwitchToo(Key, ManagerScreenState.Menu);
            return true;
        }
        public bool SwitchToMainMenu()
        {
            if(_MainMenuScreen == null)
                throw new IvoryFrameworkError(Errors.NoMainMenuException);

            SwitchToo(null, ManagerScreenState.MainMenu);
            return true;
        }        

        public object GetCurrentScreen() {
            switch (_mScreenState)
            {
                case ManagerScreenState.Game: return _gameScreens[_screenKeyHolder];
                case ManagerScreenState.Menu: return _menuScreens[_screenKeyHolder];
                default: throw new IvoryFrameworkError(Errors.InvalidUseException);
            }
        }
        public Screenbase GetScreen(string key)
        {
            if (_gameScreens.ContainsKey(key))
                return _gameScreens[key];
            else if (_menuScreens.ContainsKey(key))
                return _menuScreens[key];

            throw new Exception("0x001");
        }

        #endregion

        #region @@## Graphics Management ##@@
        private void setupFromConfig()
        {
            if (_gdm == null)
                _gdm = new GraphicsDeviceManager(Game);

            _gdm.PreferredBackBufferWidth = Config.GetValueByFlag<int>("width");
            _gdm.PreferredBackBufferHeight = Config.GetValueByFlag<int>("height");
            _gdm.IsFullScreen = Config.GetValueByFlag<bool>("fullscreen");
            _gdm.GraphicsProfile = (Config.GetValueByFlag<bool>("hidef") ? GraphicsProfile.HiDef : GraphicsProfile.Reach);
            _gdm.PreferMultiSampling = Config.GetValueByFlag<bool>("multisampling");
            Game.IsMouseVisible = Config.GetValueByFlag<bool>("mousevisible");
            _gdm.ApplyChanges();            
            _ManagerLog.Write("Settings loaded & applied from config");
        }
        
        public void ChangeGraphics(int width, int height, bool fullscreen)
        {
            _gdm.PreferredBackBufferWidth = width;
            _gdm.PreferredBackBufferHeight = height;
            _gdm.IsFullScreen = fullscreen;
            _gdm.ApplyChanges();

            Config.EditFlagValue<int>("width", width);
            Config.EditFlagValue<int>("height", height);
            Config.EditFlagValue<bool>("fullscreen", fullscreen);
            _ManagerLog.Write("Graphics Settings Changed");
        }
        public Rectangle WindowRect
        {
            get { return new Rectangle(0, 0, _gdm.PreferredBackBufferWidth, _gdm.PreferredBackBufferHeight); }
        }
        public Vector2 WindowRez
        {
            get { return new Vector2(_gdm.PreferredBackBufferWidth, _gdm.PreferredBackBufferHeight); }
        }
        #endregion

        private void OnGameExiting(object sender, EventArgs e) {
            //save out config file
            Config.SaveConfigFile();

            //Save out debug log streams
        }

        public override void Draw(GameTime gameTime)
        {
            RenderScreens(gameTime);
            base.Draw(gameTime);
        }
        public override void Update(GameTime gameTime)
        {
            _InputController.Update(gameTime);
            UpdateScreens(gameTime);
            base.Update(gameTime);
        }

        private void RenderScreens(GameTime time)
        {
            switch (_mScreenState) 
            {
                case ManagerScreenState.Splashes: _splashScreens[_splashCounter].drawScreen(_BatchDevice, time); break;                
                case ManagerScreenState.MainMenu:
                    {
                        if (_MainMenuScreen == null)
                            throw new IvoryFrameworkError(Errors.NoMainMenuException);

                        _MainMenuScreen.drawScreen(_BatchDevice, time); break;
                    }
                case ManagerScreenState.Menu: _menuScreens[_screenKeyHolder].drawScreen(_BatchDevice, time); break;
                case ManagerScreenState.Game: _gameScreens[_screenKeyHolder].drawScreen(_BatchDevice, time); break;               
            }               
            
        }
        private void UpdateScreens(GameTime time)
        {
            switch (_mScreenState)
            {
                case ManagerScreenState.Splashes:
                    {
                        if (_splashScreens.Count == 0)
                            SwitchToMainMenu();
                        else
                        {
                            _splashScreens[_splashCounter].updateScreen(time, _InputController);
                            if (_splashScreens[_splashCounter].IsFinished)
                            {
                                _splashCounter++;
                                if (_splashCounter >= _splashScreens.Count)
                                {
                                    SwitchToMainMenu();
                                }
                            }
                        }
                        break;
                    }
                
                case ManagerScreenState.MainMenu:
                    {
                        if (_MainMenuScreen == null)
                            throw new IvoryFrameworkError(Errors.NoMainMenuException);

                        _MainMenuScreen.updateScreen(time,_InputController); break;
                    }

                case ManagerScreenState.Menu: _menuScreens[_screenKeyHolder].updateScreen(time, _InputController); break;
                case ManagerScreenState.Game: _gameScreens[_screenKeyHolder].updateScreen(time, _InputController); break;
            } 
        }

        public void LoadScreenManager() {
            _BatchDevice = new SpriteBatch(Game.GraphicsDevice); 
                        
            //Load all spalash screens
            List<Screenbase> sbList = new List<Screenbase>();
            sbList.AddRange(_splashScreens);
            sbList.AddRange(_menuScreens.Values);
            sbList.AddRange(_gameScreens.Values);

            if (_MainMenuScreen == null)
                throw new IvoryFrameworkError(Errors.NoMainMenuException);

            sbList.Add(_MainMenuScreen);

            foreach (Screenbase b in sbList)
                b.preLoad();

            _ManagerLog.Write("Number of screens Loaded: " + sbList.Count);
        }
    }
}
